我是 git 新手,不太清楚 stash 的工作原理。
假设我正在处理分支 master 并尝试git pull接收错误,即我的本地更改将被覆盖并且需要隐藏或提交。如果我没有暂存我的任何更改并运行git stash,然后执行git pull并成功更新,当我发生什么git stash apply?
git pull
git stash
git stash apply
一般来说,如果其他人修改文件并且我运行git pull,当我运行时会发生什么run git stash apply?它是否会覆盖刚刚更新的文件,无论它们在我存储它们时是否已上演?它会用git pull隐藏的文件覆盖我刚刚更新的每个文件吗?
run git stash apply
git stash在当前提交上挂起一个 stash-bag——这是一种特殊的合并提交形式,它不在任何分支上HEAD。稍后git stash apply,当您进行任何提交时(可能是不同的提交),然后尝试通过查看挂起的 stash-bag 和挂起它的提交来恢复 git 计算的更改。
HEAD
完成更改后,您应该使用git stash drop它从“隐藏”的提交中释放 stash-bag。(而且,git stash pop只是“应用,然后自动删除”的简写。不过,我建议将这两个步骤分开,以防您不喜欢“应用”的结果并且想稍后再试。)
git stash drop
git stash pop
git stash实际上相当复杂。
有人说“一旦你理解了 X,git 就会变得更有意义”,对于“X”的许多不同值,这概括为“一旦你理解了 git,git 就会变得更有意义”。:-)
在这种情况下,要真正理解stash,您需要了解提交、分支、索引/暂存区域、git 的引用名称空间和合并所有工作的方式,因为git stash创建了一个非常特殊的合并提交,它由外部名称引用通常的命名空间——一种奇怪的合并,根本不是“在一个分支上”——并git stash apply使用 git 的合并机制来尝试“重新应用”在进行特殊合并提交时保存的更改,可选地保留区别在分阶段和非分阶段的变化之间。
stash
幸运的是,您实际上不需要了解所有这些内容即可使用 git stash。
在这里,您正在处理某个分支 ( master) 并且您有一些尚未准备好的更改,因此您不想在分支上提交它们。1 同时,其他人将一些好的东西——或者至少,你希望它是好的——放到origin/master远程仓库的 over,所以你想把它们捡起来。
master
origin/master
假设您和他们都从以 结尾的提交开始- A - B - C,即,C当您开始在分支上工作时,这是您在 repo 中的最终提交master。新的“好东西”提交,我们将调用Dand E。
- A - B - C
C
D
E
在您的情况下,您正在运行git pull并且由于“工作目录不干净”问题而失败。所以,你跑git stash。这会以其特殊的怪异 stash-y 方式为您提交您的东西,因此您的工作目录现在是干净的。现在你可以了git pull。
就提交的绘制而言(使用gitkor得到的图形git log --graph),您现在有了这样的东西。stash 是你运行时在分支i-w中“打开”的提交的小袋子。(名称的原因是这些是存储的“i”索引/暂存区域和“w”ork-tree 部分。)master``git stash``i``w
gitk
git log --graph
i-w
master``git stash``i``w
- A - B - C - D - E <-- HEAD=master, origin/master |\ i-w <-- the "stash"
如果您开始工作master并且从未进行任何提交,那么您会得到这张图。您最近的提交是因此C。进行存储后,git pull能够添加提交D并添加E到您的本地分支master。隐藏的工作包仍然挂着C。
如果你自己做了一些提交——我们将它们称为Y,用于你的提交,并且Z只是为了有两个提交——“stash then pull”的结果如下所示:
Y
Z
.-------- origin/master - A - B - C - D - E - M <-- HEAD=master \ / Y - Z |\ i-w <-- the "stash"
这一次,在stash挂掉它的存储袋之后Z,pull——就在fetch那时merge——必须进行真正的合并,而不仅仅是“快进”。所以它使 commit M,合并提交。origin/master标签仍然是指 commit ,而E不是M。您现在master处于 commit状态M,这是Eand的合并Z。你“领先”了origin/master。
pull
fetch
merge
M
在任何一种情况下,如果您现在运行git stash applystash 脚本(它是一个使用许多低级 git“管道”命令的 shell 脚本),2会有效地执行以下操作:
git diff stash^ stash > /tmp/patch git apply /tmp/patch
这个 diffs stash,它将w存储的“工作树”部分命名为正确的3父级。换句话说,它会找出正确的父提交(C或Z,视情况而定)和隐藏的工作树之间的“你改变了什么”。然后它将更改应用于当前签出的版本,即E或M,再次取决于您的开始位置。
w
顺便说一句,git stash show -p实际上只是运行相同的git diff命令(当然没有> /tmp/patch部分)。没有-p,它运行 diff --stat。因此,如果您想详细了解git stash apply将合并的内容,请使用git stash show -p. (不过,这不会向您展示git stash apply可以尝试从存储的索引部分应用什么;这是我对存储脚本的一个小抱怨。)
git stash show -p
git diff
> /tmp/patch
-p
--stat
在任何情况下,一旦 stash 干净地应用,您可以使用git stash drop删除对 stash-bag 的引用,以便对其进行垃圾收集。在你放弃它之前refs/stash,它有一个名字(它的名字变成) 并让新的 stash 使用这个名字。大多数 reflog 条目会保留 90 天(您可以将其配置为不同)然后过期。默认情况下,存储不会过期,但如果您以其他方式配置,“推送”存储可能会丢失,因此如果您开始根据自己的喜好配置 git,请小心依赖“永久保存”。stash@{0}``stash``stash@{1}``refs/stash
refs/stash
stash@{0}``stash``stash@{1}``refs/stash
请注意,git stash drop此处“弹出”存储堆栈,重新编号stash@{2}并stash@{1}使其stash@{1}变为 plain stash。用于git stash list查看存储堆栈。
stash@{2}
stash@{1}
git stash list
1无论如何都继续提交它们也不错,然后稍后git rebase -i再压缩或修复第二个、第三个、第四个、…、第 n 个提交,和/或重写临时“检查点”提交。但这与此无关。
git rebase -i
2它稍微复杂一些,因为您可以--index尝试使用暂存的更改来暂存,但实际上,如果您查看脚本,您会看到实际的命令序列git diff ... | git apply --index。在这种情况下,它确实只是应用了差异!不过,最终它会git merge-recursive直接调用以合并到工作树中,从而允许从其他地方引入相同的更改。git apply如果你的补丁做了一些“好东西”提交D并且E也做了的事情,那么一个普通的将会失败。
--index
git diff ... | git apply --index
git merge-recursive
git apply
3stash这使用了 git 的父命名魔法语法,并在脚本内部进行了一些预先规划。因为 stash 是这个时髦的合并提交,w有两个甚至三个父级,但是 stash 脚本设置它,以便“第一个父级”是原始提交,C或者Z,视情况而定。“第二个父级”stash^2是提交时的索引状态,如i悬挂的小存储袋中所示,“第三个父级”(如果存在)是未暂存且可能被忽略的文件,来自git stash save -u或git stash save -a.
stash^2
i
git stash save -u
git stash save -a
请注意,在此答案中,我假设您没有仔细暂存工作树的一部分,并且您没有使用git stash apply --index它来恢复暂存索引。通过不执行任何这些操作,您会使提交变得非常多余,因此我们在此步骤i中无需担心它。apply如果您正在使用apply --index或等效,并且有暂存的项目,您可能会遇到更多的极端情况,其中存储不会干净地应用。
git stash apply --index
apply
apply --index
这些相同的警告适用于使用-uor保存的具有-a第三次提交的存储,还有更多的极端情况。
-u
-a
对于这些特别困难的情况,git stash提供了一种将存储转换为成熟分支的方法——但我将把所有这些留给另一个答案。